home *** CD-ROM | disk | FTP | other *** search
/ Celestin Apprentice 7 / Apprentice-Release7.iso / Source Code / C / Frameworks / Hsoi's App Shell 1.0a4 / Hsoi's App Shell Source / HASSoundSpeech.c < prev    next >
Encoding:
C/C++ Source or Header  |  1997-01-28  |  47.2 KB  |  1,844 lines  |  [TEXT/CWIE]

  1. /*    
  2.     HASSoundSpeech.c from Hsoi's App Shell © 1995-1997 John C. Daub.  All rights reserved.
  3.     
  4.     This file contains the functions dealing with text-to-speech and any of my own
  5.     sound handling functions and notes (i.e. stuff not in Michael Kamprath's WASTE
  6.     Object Handlers library, a library used by Hsoi's App Shell to handle objects
  7.     like PICTs, snds, HFS objects, etc.).
  8.     
  9.     Do see Michael's library, most notably the WE_snd_Handler.c and .h files for
  10.     more information
  11. */
  12.  
  13. /*
  14.     The text-to-speech code in here (both the dialog and the actual code to
  15.     do text to speech) are based upon Tom Bender's Tex-Edit+ 1.6.3 source
  16.     code.  I translated to C (from Pascal), made appropriate modifications,
  17.     and any other neccessary adjustments to work in the scope of Hsoi's App Shell.
  18.     
  19.     NOTE: (temporary note)  until i get all of Tom's code translated and things
  20.     working right, i'm doing it all just like Tom did it...the ResID's are the
  21.     same, everything is the same (except perhaps some of the strings).  i will
  22.     also be having the speech options dialog be a menu item for now, but i do
  23.     want to make it part of the prefs dialogs eventually.
  24. */
  25.  
  26. #pragma mark ••• #includes •••
  27.  
  28. #ifndef _WASTE_
  29. #include "WASTE.h"
  30. #endif
  31.  
  32. #include "HASGlobals.h"
  33. #include "HASMain.h"
  34. #include "HASMenus.h"
  35. #include "HASUtilDialogs.h"
  36. #include "HASUtilPStrings.h"
  37. #include "HASUtilities.h"
  38. #include "HASMovableModal.h"
  39. #include "HASSoundSpeech.h"
  40.  
  41. #include "WASTE_Objects.h"
  42.  
  43. #ifndef __SPEECH__
  44. #include <Speech.h>
  45. #endif
  46.  
  47. #ifndef __TEXTEDIT__
  48. #include <TextEdit.h>
  49. #endif
  50.  
  51.  
  52. #pragma mark -
  53. #pragma mark ••• Constants •••
  54.  
  55. enum {
  56.     inSpacers = 1,
  57.     inEnders = 2
  58. };
  59.  
  60. const char  kSpacers[] = { ' ', '.', '!', '?', '…', 9 /*tab*/, 13 /*cr*/, \
  61.                                 '3' /*^C*/, 41 /*)*/, 93 /*]*/, 125 /*}*/, 211 /*"*/ };
  62.  
  63. const char kSentenceEnders[] = { '.', '!', '?', '…', 13 };
  64.  
  65.  
  66. #pragma mark -
  67. #pragma mark ••• Globals •••
  68.  
  69. // these 4 globals are just temporary until i get the prefs implimented with the sound
  70.  
  71. //Fixed        sVoicePitch = 0;
  72. //Fixed        sVoiceRate = 0;
  73. //Fixed        sVoiceMod = 0;
  74. //Str255    sVoiceStr = "\p";
  75.  
  76. // shell-wide globals to help determine the state of things
  77.  
  78. SpeechChannel    gSpeechChannel = nil;
  79. HiliteMethod    gHiliteMethod = off;
  80. Boolean            gSpeechOn = false;
  81. Boolean            gHiliting = false;
  82. Boolean            gPaused = false;
  83.  
  84. // and some local globals to help us keep track of things.
  85.  
  86. long            sCBWordStart;
  87. long            sCBWordEnd;
  88. long            sCBSelStart;
  89. WindowRef        sTalkingWPtr;
  90. Handle            sSpokenTxtHdl; // space to hold text block being read
  91.  
  92.  
  93. #pragma mark -
  94. #pragma mark ••• Voice Menu •••
  95.  
  96. // this function is used to build the popup voice menu the first time the speech
  97. // options dialog is invoked.  the gala (pro) voices are put at the top and the
  98. // regular voices are put at the bottom.  (gala (pro) voices are used by Macintalk Pro)
  99.  
  100. OSErr    HsoiBuildVoiceMenu( void )
  101. {
  102.     MenuRef                voiceMenuHdl;
  103.     VoiceSpec            vSpec;
  104.     VoiceDescription    vDesc;
  105.     short                numVoices;
  106.     short                x, galaCt;
  107.     OSErr                err;
  108.     
  109.     // get the menu handle
  110.     
  111.     voiceMenuHdl = GetMenu( kPopmenuVoice );
  112.     
  113.     // count the number of available voices
  114.     
  115.     err = CountVoices( &numVoices );
  116.     
  117.     // make sure we need to build a menu
  118.     
  119.     if ( (err == noErr) && (CountMenuItems( voiceMenuHdl ) == 3 ))
  120.     {
  121.         galaCt = 0;
  122.         
  123.         if ( numVoices > kMaxVoiceCount )
  124.             numVoices = kMaxVoiceCount;
  125.         
  126.         // get each voice's name and insert it
  127.         
  128.         for ( x = 1; x <= numVoices; x++ )
  129.         {
  130.             if (err == noErr )
  131.                 err = GetIndVoice( x, &vSpec );
  132.             
  133.             if ( err == noErr )
  134.                 err = GetVoiceDescription( &vSpec, &vDesc, sizeof( VoiceDescription ) );
  135.             
  136.             if ( err == noErr )
  137.             {
  138.                 // insert pro voices at top of menu
  139.                 
  140.                 if ( vDesc.voice.creator == 'gala' )
  141.                 {
  142.                     InsertMenuItem( voiceMenuHdl, vDesc.name, 2 );
  143.                     galaCt += 1;
  144.                 }
  145.                 else
  146.                     InsertMenuItem( voiceMenuHdl, vDesc.name, 3 + galaCt );
  147.             }
  148.         }
  149.     
  150.     }
  151.     
  152.     return err;
  153. }
  154.  
  155.  
  156. // this function returns the name of the voice currently selected in the voice popup
  157. // menu (cntrlHdl).  if the default item is selected (item #1), then the empty string
  158. // is returned.
  159.  
  160. void    HsoiGetVoiceMenuSelection( Handle cntrlHdl, Str255 voiceStr )
  161. {
  162. //    Str255        voiceStr = NIL_STRING;
  163.  
  164.     short        currentVoiceItem;
  165.     MenuRef        voiceMenuHdl;
  166.     register short i;
  167.     
  168.     for ( i=0; i < 255; i++ )
  169.         voiceStr[i] = nil;
  170.     
  171.     currentVoiceItem = GetControlValue( (ControlRef)cntrlHdl );
  172.     
  173.     if ( currentVoiceItem > 2 )
  174.     {
  175.         voiceMenuHdl = GetMenu( kPopmenuVoice );
  176.         GetMenuItemText( voiceMenuHdl, currentVoiceItem, voiceStr );
  177.     }
  178.     
  179.     return;
  180. }
  181.  
  182.  
  183. // this procedure selects (checkmarks) the given voice on the menu.  it is used
  184. // when the menu is built
  185.  
  186. void    HsoiSetVoiceMenuSelection( ConstStr255Param selVoiceStr, ControlRef cntrlHdl )
  187. {
  188.     Str255        voiceStr = NIL_STRING;
  189.     short        numItems;
  190.     MenuRef        voiceMenuHdl;
  191.     short        x = 1, index = 1;
  192.     
  193.     // get the menu handle
  194.     
  195.     voiceMenuHdl = GetMenu( kPopmenuVoice );
  196.     
  197.     // count the number of items on the menu
  198.     
  199.     numItems = CountMenuItems( voiceMenuHdl );
  200.     
  201.     // scan the voice menu -- look for match for selVoiceStr
  202.     
  203.     while ( (index == 1) && ( x <= numItems ) )
  204.     {
  205.         GetMenuItemText( voiceMenuHdl, x, voiceStr );
  206.         
  207.         if ( EqualString ( selVoiceStr, voiceStr, false, false ) )
  208.             index = x;
  209.         else
  210.             x++;
  211.     }
  212.     
  213.     // now checkmark the popup menu
  214.     
  215.     (*cntrlHdl)->contrlValue = index;
  216.     
  217.     return;
  218. }
  219.  
  220.  
  221. #pragma mark -
  222. #pragma mark ••• Speech Options Dialog Utils •••
  223.  
  224. // draw the comments describing the voice which is currently selected by the popmenu
  225.  
  226. // ••• DEBUG THIS FUNCTION, STEP THROUGH LINE BY LINE, ESPECIALLY CHECKING THE
  227. // VALUES OF THE STRINGS AS WE GO ALONG.
  228.  
  229. pascal void    HsoiDrawVoiceDescBox( WindowRef dlg, short itemNo )
  230. {
  231.     GrafPtr                oldPort;
  232.     long                oldFont, oldSize;
  233.     Str255                voiceStr = NIL_STRING;
  234.     short                theVoicePref;
  235.     Rect                r;
  236.     Handle                h;
  237.     OSErr                err;
  238.     VoiceSpec            vSpec;
  239.     VoiceSpec            *vSpecPtr;
  240.     VoiceDescription    vDesc;
  241.     Str255                theGender, theAge;
  242.     Str255                yearsOldStr, theTextStr = "\p";
  243.     
  244.     // set up the ports
  245.     
  246.     GetPort( &oldPort );
  247.     SetPortWindowPort( dlg );
  248.     
  249.     // save the original font and size settings
  250.     
  251.     oldFont = GetWindowPort(dlg)->txFont;
  252.     oldSize = GetWindowPort(dlg)->txSize;
  253.     
  254.     // get the current voiceSpec and description
  255.     
  256.     h = HsoiGetDialogItemHandle( dlg, kDlgItemVoicePopmenu );
  257.     HsoiGetVoiceMenuSelection( h, voiceStr );
  258.     theVoicePref = HsoiGetVoiceIndex( voiceStr );
  259.     
  260.     // get the voice description of the default/chosen voice
  261.     
  262.     if ( theVoicePref == 0 )
  263.     {
  264.         vSpecPtr = nil;
  265.         
  266.         err = GetVoiceDescription( vSpecPtr, &vDesc, sizeof( VoiceDescription ) );
  267.     }
  268.     else
  269.     {
  270.         err = GetIndVoice( theVoicePref, &vSpec );
  271.         if ( err == noErr )
  272.             err = GetVoiceDescription( &vSpec, &vDesc, sizeof( VoiceDescription) );
  273.     }
  274.     
  275.     // assemble and display a descriptive string
  276.     
  277.     if ( err == noErr )
  278.     {
  279.         // set the font/size for drawing.
  280.         
  281.         TextSize( 9 );
  282.         TextFont( geneva );
  283.         
  284.         // get the gender in a string
  285.         
  286.         if ( vDesc.gender == kMale )
  287.             GetIndString( theGender, kVoiceStringsID, kStrMale );
  288.         else if ( vDesc.gender == kFemale )
  289.             GetIndString( theGender, kVoiceStringsID, kStrFemale );
  290.         else
  291.             GetIndString( theGender, kVoiceStringsID, kStrNeuter );
  292.         
  293.         // get the age in a string
  294.         
  295.         NumToString( (long)vDesc.age, theAge );
  296.         GetIndString( yearsOldStr, kVoiceStringsID, kStrYearsOld );
  297.         
  298.         // and take all the strings and make them into one big string
  299.  
  300.         // add the default voice name if needed
  301.         
  302.         if ( theVoicePref == 0 )
  303.         {
  304.             HsoiConcatString( theTextStr, "\p[" );
  305.             HsoiConcatString( theTextStr, vDesc.name );
  306.             HsoiConcatString( theTextStr, "\p] " );
  307.         }
  308.             
  309.         HsoiConcatString( theTextStr, theAge );
  310.         HsoiConcatString( theTextStr, yearsOldStr );
  311.         HsoiConcatString( theTextStr, theGender );
  312.         HsoiConcatString( theTextStr, "\p-- " );
  313.         HsoiConcatString( theTextStr, vDesc.comment );
  314.         
  315.         // fyi, in the end, this should read something like:
  316.         // "30 year old male.-- I sure love being inside of this computer"
  317.         // and if there is a default voice name, just prepend a "[Fred] 30 year old..."
  318.         
  319.         // draw the voice description
  320.         
  321.         // incidentally, this neat little function, TETextBox is part of TextEdit.
  322.         // you can read more about it in NIM:Text (it's a neat, handy thing).  but
  323.         // it'd be really cool if we could create a WASTE equiv of this.
  324.         
  325.         //TETextBox(const void *text, long length, const Rect *box, short just)
  326.  
  327.         HsoiGetDialogItemRect( dlg, itemNo, &r );
  328.         TETextBox( &theTextStr[1], theTextStr[0], &r, teFlushDefault );
  329.         TextFont( oldFont );
  330.         TextSize( oldSize );
  331.         
  332.         
  333.     }
  334.     
  335.     SetPort( oldPort );
  336.     
  337.     return;
  338. }
  339.  
  340. #pragma mark -
  341. #pragma mark ••• Speech Options Dialog •••
  342.  
  343. // display the speech options dialog box
  344.  
  345. void    HsoiDoSpeechOptionsDialog( void )
  346. {
  347.     long            xx;
  348.     Str255            s;
  349.     Handle            h;
  350.     OSErr            err= noErr;
  351.     DialogRef        dialog;
  352.     UserItemUPP        drawVoiceDescBoxUPP = nil;
  353.     ModalFilterUPP    filterUPP = nil;
  354.     short            item;
  355.     
  356.     // get the dialog
  357.     
  358.     dialog = GetNewDialog( kDialogSpeechOptions, nil, MOVE_TO_FRONT );
  359.     
  360.     if ( dialog == nil )
  361.     {
  362.         SysBeep( 1 );  // lame error handling
  363.         return;
  364.     }
  365.     
  366.     SetGrafPortOfDialog( dialog );
  367.     
  368.     // Tom drew the OK button's outline with his own proc...we'll just use the
  369.     // built-in Dialog Manager routines
  370.     
  371.     SetDialogDefaultItem( dialog, ok );
  372.     SetDialogCancelItem( dialog, cancel );
  373.     SetDialogTracksCursor( dialog, true );
  374.     
  375.     // get the description box's proc set up
  376.     
  377.     drawVoiceDescBoxUPP = NewUserItemProc( HsoiDrawVoiceDescBox );
  378.     HsoiSetDialogItemProc( dialog, kDlgItemVoiceDescription, drawVoiceDescBoxUPP );
  379.     
  380.     // build the voice menu
  381.     
  382.     err = HsoiBuildVoiceMenu();
  383.     
  384.     // set the voice menu to the option stored in the prefs
  385.     
  386.     // NOTE!!!!!  when i get the prefs updated, I need to update this line to
  387.     // use the value (Str255) stored in the prefs.
  388.     
  389.     if ( err == noErr )
  390.         HsoiSetVoiceMenuSelection( gMyPrefs.sVoiceStr, (ControlRef)HsoiGetDialogItemHandle( dialog, kDlgItemVoicePopmenu ) );
  391.     else
  392.     {    // do some error handling
  393.     
  394.         HsoiDisplaySpeechError( err );
  395.         goto exit;
  396.     }
  397.         
  398.     // set up pitch/rate controls
  399.     
  400.     h = HsoiGetDialogItemHandle( dialog, kDlgItemPitch );
  401.     xx = HiWrd( gMyPrefs.sVoicePitch );
  402.     NumToString( xx, s );
  403.     SetDialogItemText( h ,s );
  404.     
  405.     h = HsoiGetDialogItemHandle( dialog, kDlgItemRate );
  406.     xx = HiWrd( gMyPrefs.sVoiceRate );
  407.     NumToString( xx, s );
  408.     SetDialogItemText( h, s );
  409.     
  410.     h = HsoiGetDialogItemHandle( dialog, kDlgItemMod );
  411.     xx = HiWrd( gMyPrefs.sVoiceMod );
  412.     NumToString( xx, s );
  413.     SetDialogItemText( h, s );
  414.     
  415.     SelectDialogItemText( dialog, kDlgItemPitch, 0, MAXLONG );
  416.     
  417.     SetCursor( &qd.arrow );
  418.     
  419.     ShowWindow( GetDialogWindow( dialog ) );
  420.     SelectWindow( GetDialogWindow( dialog ) );
  421.     
  422.     gInModalState = true;
  423.     HsoiAdjustMenus();
  424.     
  425.     // INSERT MOVABLE MODAL DIALOG HANDLER HERE
  426.     
  427.     filterUPP = NewModalFilterProc( hsoiSpeechOptionsDialogFilter );
  428.     
  429.     do {
  430.         MovableModalDialog( filterUPP, &item );
  431.         
  432.         HsoiHandleSpeechOptionsDialog( item, dialog );
  433.     } while ( (item != ok) && (item != cancel ) );
  434.     
  435.     
  436.     
  437. exit:
  438.     
  439.     DisposeRoutineDescriptor( filterUPP );
  440.     filterUPP = nil;
  441.     DisposeRoutineDescriptor( drawVoiceDescBoxUPP );
  442.     drawVoiceDescBoxUPP = nil;
  443.  
  444.     DisposeDialog( dialog );
  445.     
  446.     gInModalState = false;
  447.     HsoiAdjustMenus();
  448.  
  449.     return;
  450. }
  451.  
  452.  
  453. // the speech options dialog filter.  mostly, we just check to make sure we have
  454. // something in all the edit boxes, and filter out "junk" keys.  then we call our
  455. // usual dialog filter
  456.  
  457. pascal Boolean hsoiSpeechOptionsDialogFilter( DialogRef theDialog, EventRecord *event, short *item )
  458. {
  459.     Str255        pitchStr, rateStr, modStr;
  460.     Boolean        okIsDimmed;
  461.     char        theKey;
  462.     Boolean        retval = false;
  463.     GrafPtr        oldPort;
  464.     
  465.     GetPort( &oldPort );
  466.     SetGrafPortOfDialog( theDialog );
  467.     
  468.     // see if there is text in the dialog edittext items.  if any of these
  469.     // fields are empty, we cannot allow the user to proceed, so we'll dim the OK
  470.     // button and the Sample button
  471.     
  472.     GetDialogItemText( HsoiGetDialogItemHandle( theDialog, kDlgItemPitch ), pitchStr );
  473.     GetDialogItemText( HsoiGetDialogItemHandle( theDialog, kDlgItemRate ), rateStr );
  474.     GetDialogItemText( HsoiGetDialogItemHandle( theDialog, kDlgItemMod ), modStr );
  475.     
  476.     if ( (pitchStr[0] != 0) && (rateStr[0] != 0) && (modStr[0] != 0) )
  477.     {
  478.         // we have text in all three, we can make sure OK is enabled
  479.         
  480.         HiliteControl( (ControlRef)HsoiGetDialogItemHandle( theDialog, ok ), kCtlActive );
  481.         
  482.         // make sure Sample is enabled
  483.         
  484.         HiliteControl( (ControlRef)HsoiGetDialogItemHandle( theDialog, kDlgItemSample ), kCtlActive );
  485.         okIsDimmed = false;
  486.     }
  487.     else
  488.     {
  489.         // something must be empty, dim OK
  490.         
  491.         HiliteControl( (ControlRef)HsoiGetDialogItemHandle( theDialog, ok ), kCtlInactive );
  492.         HiliteControl( (ControlRef)HsoiGetDialogItemHandle( theDialog, kDlgItemSample ), kCtlInactive );
  493.         okIsDimmed = true;
  494.     }
  495.     
  496.     // now we'll check for key related events
  497.     
  498.     if ( (event->what == keyDown) || (event->what == autoKey ) )
  499.     {
  500.         theKey = event->message & charCodeMask;
  501.         
  502.         // if the return or enter key was hit and OK is dimmed, we need to snarf
  503.         // that keypress ourselves
  504.         
  505.         if ( ((theKey == kEnterKey) || (theKey == kReturnKey)) && okIsDimmed )
  506.         {
  507.             retval = true;
  508.             SysBeep( 3 ); // just to give some sort of feedback
  509.             goto exit;
  510.         }
  511.         else
  512.             retval = false;
  513.         
  514.         // and let's snarf some other things....if it's not one of a few certain keys,
  515.         // we'll complain.  The user only needs a few certain keys (numbers, return, etc)
  516.         // to deal with this dialog...and by restricting them somewhat, it can make our
  517.         // lives easier in ensuring that they don't enter any invalid stuff
  518.         
  519.         if ( !(theKey > 0x2F && theKey < 0x3A ) &&   // if it wasn't a number
  520.                 ( theKey != kEnterKey ) &&             // if it wasn't the Enter key
  521.                 ( theKey != kReturnKey ) &&             // if it wasn't the Return key
  522.                 ( theKey != kEscKey ) &&             // if it wasn't Escape
  523.                 ( theKey != kBackSpace ) &&             // if it wasn't backspace
  524.                 ( theKey != kDeleteKey ) &&             // if it wasn't the delete key
  525.                 ( theKey != kTabKey ) &&             // if it wasn't the tab key
  526.                 ( theKey != kLeftArrow ) &&             // and all the arrow keys for easy editing
  527.                 ( theKey != kRightArrow ) &&
  528.                 ( theKey != kUpArrow ) &&
  529.                 ( theKey != kDownArrow ) )
  530.         {
  531.             SysBeep( 5 );
  532.             retval = true;    // we've handled it
  533.         }
  534.         else if ( theKey > 0x2F && theKey < 0x3A ) // if it's a number key
  535.         {
  536.             // things are a bit tricky now.  we know the key is one of the allowed keys.
  537.             // but!  we want to restrict the length of the strings entered by the user.
  538.             // using the up/down arrows, if you've hit the upper or lower limit, it'll
  539.             // just sit there...the value won't increase any more.
  540.             
  541.             // but, this would still allow the user to type in something like: 10000000
  542.             
  543.             // that's not quite a valid value.  now, it doesn't really matter if this
  544.             // happens cause HsoiGetDialogItemValue, if it receives a value greater than
  545.             // or less than the upper/lower limits, it'll just pin the return value to
  546.             // the limit.  so from a code level, we'll never have values outside of
  547.             // the valid range.
  548.             
  549.             // however, this might confuse the user... "I entered 5 billion, why isn't
  550.             // it speaking with that???"
  551.             
  552.             // so, what we'll do here is limit what the user can enter in terms of
  553.             // text.  it's nice that the upper limits of the values (kMaxPitch, kMaxRate
  554.             // and kMaxMod) are all 3 digit numbers.  so, what we can do is check to
  555.             // see if we'll be moving beyond that (i.e. limit the text entry to no more
  556.             // than 3 characters).
  557.             
  558.             // in doing this, now not only do we have to check the length of the
  559.             // existing text, but there is also the chance there could be a selection
  560.             // active, and if so, that will replace what we have, so who knows how
  561.             // things could go!  this might get hairy....
  562.             
  563.             Str255        s; // the text of the currently active edit line
  564.             
  565.             // now, get the text of the currently active edit line
  566.             
  567.             GetDialogItemText( HsoiGetDialogItemHandle( theDialog, GetDialogKeyboardFocusItem( theDialog )), s );
  568.             
  569.             // see if there is a selection range
  570.             
  571.             if ( HsoiHasSelectionRange( (DialogPeek)theDialog ) )
  572.             {
  573.                 // it does...so the limit buffer doesn't really matter.  if one charcter
  574.                 // is selected, it'll just replace it, no biggie.  if more than one
  575.                 // character is selected, the total number of characters in the
  576.                 // edittext box will drop, so again, no biggie
  577.         
  578.                 retval = false; // let the Dialog Manager handle it
  579.             }
  580.             else
  581.             {
  582.                 // there isn't a selection range, so we'll just check how big the
  583.                 // string currently is.  if it's 3 or more, beep, else let it through
  584.                 // (remember, we can check for "3" only cause all the pitch/rate/mod
  585.                 // max's are 3 digits...a nice and by-chance thing
  586.                 
  587.                 if ( s[0] >= 3 )
  588.                 {
  589.                     SysBeep( 3 );
  590.                     retval = true; // we handled it
  591.                 }
  592.                 
  593.                 // now, all of that probably seemed really unnecessary...like checking
  594.                 // for a selection range.  but if you have the ability to paste/copy/cut
  595.                 // stuff (mostly paste) into your dialogs, this will have to be
  596.                 // checked for (see the big modal dialog's filter for an example)
  597.             
  598.             
  599.             }
  600.         }
  601.         else
  602.             retval = false; // let the dialog manager handle it
  603.     }
  604.     
  605.     
  606. exit:
  607.     // that's about all we need to handle specially...just call our normal dialog filter
  608.     // (and rememeber that this filter was written to be used with HASMovableModal.c
  609.     // (based upon Marco Piovanelli's MovableModal Libary 2.0).  this is important
  610.     // cause we're leaving out stuff like dealing with drags, mouse clicks, etc)
  611.     
  612.     if ( !retval )
  613.         retval = hsoiMyStandardDialogFilter( theDialog, event, item );
  614.     
  615.     SetPort( oldPort );
  616.     
  617.     return retval;
  618. }
  619.  
  620.  
  621. // handle action in the speech options dialog
  622.  
  623. void    HsoiHandleSpeechOptionsDialog( short theItem, DialogRef dlg )
  624. {
  625.     Str255    s;
  626.     VoiceSpec    vSpec;
  627.     Fixed        thePitchPref = 0, theRatePref = 0, theModPref = 0;
  628.     double        xx;
  629.     long        x;
  630.     short        currVoiceItem;    // selected menu item
  631.     short        i;
  632.     OSErr        err = noErr;
  633.     Boolean        legal;
  634.  
  635.     switch( theItem )
  636.     {
  637.         case ok:
  638.         {
  639.             // get pitch/rate/mod and convert to Fixed
  640.             
  641.             HsoiGetDialogItemValue( dlg, kDlgItemPitch, kMinPitch, kMaxPitch, &legal, &xx );
  642.             x = xx;
  643.             //if ( legal )
  644.                 gMyPrefs.sVoicePitch = BSL( x, 16 );
  645.             
  646.             HsoiGetDialogItemValue( dlg, kDlgItemRate, kMinRate, kMaxRate, &legal, &xx );
  647.             x = xx;
  648.             //if ( legal )
  649.                 gMyPrefs.sVoiceRate = BSL( x, 16 );
  650.             
  651.             HsoiGetDialogItemValue( dlg, kDlgItemMod, kMinMod, kMaxMod, &legal, &xx );
  652.             x = xx;
  653.             //if ( legal )
  654.                 gMyPrefs.sVoiceMod = BSL( x, 16 );
  655.                 
  656.             // get the chosen voice and remember as globals
  657.             
  658.             HsoiGetVoiceMenuSelection( HsoiGetDialogItemHandle( dlg, kDlgItemVoicePopmenu ), gMyPrefs.sVoiceStr );
  659.             
  660.             if ( err == noErr )
  661.                 err = SetSpeechRate( gSpeechChannel, gMyPrefs.sVoiceRate );
  662.             if ( err == noErr )
  663.                 err = SetSpeechPitch( gSpeechChannel, gMyPrefs.sVoicePitch );
  664.             if ( err == noErr )
  665.                 err = SetSpeechInfo( gSpeechChannel, soPitchMod, &gMyPrefs.sVoiceMod );
  666.                 
  667.             // the prefs have changed
  668.             
  669.             gWritePrefs = true;
  670.             
  671.             // hide the dialog
  672.             
  673. //            HideWindow( GetDialogWindow( dlg ) );
  674.         }
  675.         break; // end: case ok
  676.         
  677.         case cancel:
  678.         {
  679.             err = DisposeSpeechChannel( gSpeechChannel );
  680.             
  681.             // restore previous speech channel
  682.             
  683.             i = HsoiGetVoiceIndex( gMyPrefs.sVoiceStr );
  684.             
  685.             if ( i == 0 )
  686.                 err = NewSpeechChannel( nil, &gSpeechChannel );
  687.             else
  688.             {
  689.                 if ( err == noErr )
  690.                     err = GetIndVoice( i, &vSpec );
  691.                 if ( err == noErr )
  692.                     err = NewSpeechChannel( &vSpec, &gSpeechChannel );
  693.             }
  694.         
  695.             if ( err == noErr )
  696.                 err = SetSpeechRate( gSpeechChannel, gMyPrefs.sVoiceRate );
  697.             if ( err == noErr )
  698.                 err = SetSpeechPitch( gSpeechChannel, gMyPrefs.sVoicePitch );
  699.             if ( err == noErr )
  700.                 err = SetSpeechInfo( gSpeechChannel, soPitchMod, &gMyPrefs.sVoiceMod );
  701.                 
  702.             // hide the dialog
  703.             
  704.         //    HideWindow( GetDialogWindow( dlg ) );
  705.         
  706.         }
  707.         break; // end: case cancel
  708.     
  709.         case kDlgItemPitchUp:
  710.             HsoiGetDialogItemValue( dlg, kDlgItemPitch, kMinPitch, kMaxPitch, &legal, &xx );
  711.             HsoiDoArrowUpDown( dlg, kDlgItemPitchUp, kDlgItemPitch, kDlgItemPitchArrows, 1, kMinPitch, kMaxPitch, false, &xx );
  712.         break;
  713.         
  714.         case kDlgItemPitchDown:
  715.             HsoiGetDialogItemValue( dlg, kDlgItemPitch, kMinPitch, kMaxPitch, &legal, &xx );
  716.             HsoiDoArrowUpDown( dlg, kDlgItemPitchDown, kDlgItemPitch, kDlgItemPitchArrows, -1, kMinPitch, kMaxPitch, false, &xx );
  717.         break;
  718.         
  719.         case kDlgItemRateUp:
  720.             HsoiGetDialogItemValue( dlg, kDlgItemRate, kMinRate, kMaxRate, &legal, &xx );
  721.             HsoiDoArrowUpDown( dlg, kDlgItemRateUp, kDlgItemRate, kDlgItemRateArrows, 1, kMinRate, kMaxRate, false, &xx );
  722.         break;
  723.         
  724.         case kDlgItemRateDown:
  725.             HsoiGetDialogItemValue( dlg, kDlgItemRate, kMinRate, kMaxRate, &legal, &xx );
  726.             HsoiDoArrowUpDown( dlg, kDlgItemRateDown, kDlgItemRate, kDlgItemRateArrows, -1, kMinRate, kMaxRate, false, &xx );
  727.         break;
  728.         
  729.         case kDlgItemModUp:
  730.             HsoiGetDialogItemValue( dlg, kDlgItemMod, kMinMod, kMaxMod, &legal, &xx );
  731.             HsoiDoArrowUpDown( dlg, kDlgItemModUp, kDlgItemMod, kDlgItemModArrows, 1, kMinMod, kMaxMod, false, &xx );
  732.         break;
  733.         
  734.         case kDlgItemModDown:
  735.             HsoiGetDialogItemValue( dlg, kDlgItemMod, kMinMod, kMaxMod, &legal, &xx );
  736.             HsoiDoArrowUpDown( dlg, kDlgItemModDown, kDlgItemMod, kDlgItemModArrows, -1, kMinMod, kMaxMod, false, &xx );
  737.         break;
  738.         
  739.         case kDlgItemSample:
  740.         {
  741.             Boolean    reset;
  742.             
  743.             // get the currently selected speech parameters and voiceSpec
  744.             // if the number in the edit box is greater/less than the max/min for
  745.             // that parameter, change the value in the edit box to the apporpriate
  746.             // min/max
  747.             
  748.             GetDialogItemText( HsoiGetDialogItemHandle( dlg, kDlgItemPitch ), s );
  749.             StringToNum( s, &x );
  750.             if ( x < kMinPitch )
  751.             {
  752.                 x = kMinPitch;
  753.                 reset = true;
  754.             }
  755.             else if ( x > kMaxPitch )
  756.             {
  757.                 x = kMaxPitch;
  758.                 reset = true;
  759.             }
  760.             else
  761.                 reset = false;
  762.             
  763.             if ( reset )
  764.             {
  765.                 NumToString( x, s );
  766.                 SetDialogItemText( HsoiGetDialogItemHandle( dlg, kDlgItemPitch ), s );
  767.             }
  768.             
  769.             HsoiGetDialogItemValue( dlg, kDlgItemPitch, kMinPitch, kMaxPitch, &legal, &xx );
  770.             x = xx;
  771.             thePitchPref = BSL( x, 16 );
  772.             
  773.             GetDialogItemText( HsoiGetDialogItemHandle( dlg, kDlgItemRate ), s );
  774.             StringToNum( s, &x );
  775.             if ( x < kMinRate )
  776.             {
  777.                 x = kMinRate;
  778.                 reset = true;
  779.             }
  780.             else if ( x > kMaxRate )
  781.             {
  782.                 x = kMaxRate;
  783.                 reset = true;
  784.             }
  785.             else
  786.                 reset = false;
  787.             
  788.             if ( reset )
  789.             {
  790.                 NumToString( x, s );
  791.                 SetDialogItemText( HsoiGetDialogItemHandle( dlg, kDlgItemRate ), s );
  792.             }
  793.             
  794.             HsoiGetDialogItemValue( dlg, kDlgItemRate, kMinRate, kMaxRate, &legal, &xx );
  795.             x = xx;
  796.             theRatePref = BSL( x, 16 );
  797.             
  798.             GetDialogItemText( HsoiGetDialogItemHandle( dlg, kDlgItemMod ), s );
  799.             StringToNum( s, &x );
  800.             if ( x < kMinMod )
  801.             {
  802.                 x = kMinMod;
  803.                 reset = true;
  804.             }
  805.             else if ( x > kMaxMod )
  806.             {
  807.                 x = kMaxMod;
  808.                 reset = true;
  809.             }
  810.             else
  811.                 reset = false;
  812.             
  813.             if ( reset )
  814.             {
  815.                 NumToString( x, s );
  816.                 SetDialogItemText( HsoiGetDialogItemHandle( dlg, kDlgItemMod ), s );
  817.             }
  818.             
  819.             HsoiGetDialogItemValue( dlg, kDlgItemMod, kMinMod, kMaxMod, &legal, &xx );
  820.             x = xx;
  821.             theModPref = BSL( x, 16 );
  822.             
  823.             HsoiGetVoiceMenuSelection( HsoiGetDialogItemHandle( dlg, kDlgItemVoicePopmenu ), s );
  824.             
  825.             currVoiceItem = HsoiGetVoiceIndex( s );
  826.             
  827.             if ( currVoiceItem > 0 )
  828.                 err = GetIndVoice( currVoiceItem, &vSpec );
  829.             
  830.             // reset the speech channel using the new speech parameters
  831.             
  832.             // REMOVE THIS...USE THE COMMENT STORED IN EACH VOICE
  833.             
  834.             //HsoipStringCopy( "\pThe quick brown fox jumped over the lazy dog", s );
  835.             
  836.             if ( err == noErr )
  837.                 err = DisposeSpeechChannel( gSpeechChannel );
  838.             
  839.             if ( err == noErr )
  840.             {
  841.                 if ( currVoiceItem > 0 )
  842.                     err = NewSpeechChannel( &vSpec, &gSpeechChannel );
  843.                 else
  844.                     err = NewSpeechChannel( nil, &gSpeechChannel );
  845.             }
  846.             
  847.             if ( err == noErr )
  848.                 err = SetSpeechRate( gSpeechChannel, theRatePref );
  849.             if ( err == noErr )
  850.                 err = SetSpeechPitch( gSpeechChannel, thePitchPref );
  851.             if ( err == noErr )
  852.                 err = SetSpeechInfo( gSpeechChannel, soPitchMod, &theModPref );
  853.             
  854.             if ( err == noErr )
  855.             {
  856.                 VoiceDescription    vDesc;
  857.                 short                 len;
  858.                 VoiceSpec            *vSpecPtr;
  859.  
  860.                 if ( currVoiceItem > 0 )
  861.                     err = GetVoiceDescription( &vSpec, &vDesc, sizeof( VoiceDescription) );
  862.                 else
  863.                 {
  864.                     vSpecPtr = nil;
  865.                     err = GetVoiceDescription( vSpecPtr, &vDesc, sizeof( VoiceDescription ) );
  866.                 }
  867.                 //short    len = s[0];
  868.                 //err = SpeakText( gSpeechChannel, (char *)&s[1], len );
  869.                 
  870.                 //HsoipStringCopy( vDesc.comment, s );
  871.                 len = vDesc.comment[0];        
  872.                 err = SpeakText( gSpeechChannel, (char *)&vDesc.comment[1], len );
  873.                 while ( SpeechBusy() )
  874.                     ; // do nothing, just wait for the sample to finish
  875.             }
  876.             
  877.             HsoiDisplaySpeechError( err );
  878.             
  879.         }
  880.         break; // end: case kDlgItemSample
  881.         
  882.         case kDlgItemVoicePopmenu:
  883.         {
  884.             // get current speech parameters -- reset the speech channel
  885.             
  886.             HsoiGetVoiceMenuSelection( HsoiGetDialogItemHandle( dlg, kDlgItemVoicePopmenu ), s );
  887.             currVoiceItem = HsoiGetVoiceIndex( s );
  888.             
  889.             // if currVoiceItem = 0, then using default voice
  890.             
  891.             if ( currVoiceItem > 0 )
  892.                 err = GetIndVoice( currVoiceItem, &vSpec );
  893.             if ( err == noErr )
  894.                 err = DisposeSpeechChannel( gSpeechChannel );
  895.             if ( err == noErr )
  896.             {
  897.                 if ( currVoiceItem > 0 )
  898.                     err = NewSpeechChannel( &vSpec, &gSpeechChannel );
  899.                 else
  900.                     err = NewSpeechChannel( nil, &gSpeechChannel );
  901.             }
  902.             
  903.             // get default rate/pitch/modulation
  904.             
  905.             if ( err == noErr )
  906.                 err = GetSpeechRate( gSpeechChannel, &theRatePref );
  907.             if ( err == noErr )
  908.                 err = GetSpeechPitch( gSpeechChannel, &thePitchPref );
  909.             if ( err == noErr )
  910.                 err = GetSpeechInfo( gSpeechChannel, soPitchMod, &theModPref );
  911.             
  912.             // show correct default settings
  913.             
  914.             if ( err == noErr )
  915.             {
  916.                 xx = HiWrd( thePitchPref );
  917.                 NumToString( xx, s );
  918.                 SetDialogItemText( HsoiGetDialogItemHandle( dlg, kDlgItemPitch ), s );
  919.                 
  920.                 xx = HiWrd( theRatePref );
  921.                 NumToString( xx, s );
  922.                 SetDialogItemText( HsoiGetDialogItemHandle( dlg, kDlgItemRate ), s );
  923.                 
  924.                 xx = HiWrd( theModPref );
  925.                 NumToString( xx, s );
  926.                 SetDialogItemText( HsoiGetDialogItemHandle( dlg, kDlgItemMod ), s );
  927.                 
  928.                 SelectDialogItemText( dlg, kDlgItemPitch, 0, 0 );
  929.                 
  930.                 // draw new comment box
  931.                 
  932.                 HsoiDrawVoiceDescBox( dlg, kDlgItemVoiceDescription );
  933.             
  934.             
  935.             }
  936.         
  937.         }
  938.         break; // end: case kDlgItemVoicePopmenu
  939.     
  940.     } // end: switch( theItem )
  941.     
  942.     HsoiDisplaySpeechError( err );
  943.     
  944.     return;
  945. }
  946.  
  947.  
  948. #pragma mark -
  949. #pragma mark •••• Speech Utils •••••
  950.  
  951. void    HsoiDisplaySpeechError( OSErr err )
  952. {
  953.     Str255        s;
  954.     short        result;
  955.     
  956.     if ( err != noErr )
  957.     {
  958.         NumToString( err, s );
  959.         ParamText( s, NIL_STRING, NIL_STRING, NIL_STRING );
  960.         result = Alert( kSpeechErrorAlert, HsoiGetMyStandardDialogFilter() );
  961.     }
  962.     
  963.     return;
  964. }
  965.  
  966.  
  967. void    HsoiInitSpeechStuff( void )
  968. {
  969.     qd.randSeed = TickCount();        // seed for rnd#
  970.     
  971.     gSpeechOn = false;                // speech on/off
  972.     
  973.     gHiliting = false;                // not reading yet
  974.     
  975.     gHiliteMethod = off;            // no autohiliting yet
  976.     
  977.     gPaused = false;                // not paused yet
  978.     
  979.     gSpeechChannel = nil;            // no open speech channel yet
  980.     
  981.     sTalkingWPtr = nil;                // which window is talking?
  982.     
  983.     sSpokenTxtHdl = nil;
  984.     
  985.     sCBSelStart = 0;                // used by callback to store starting position
  986.     
  987.     return;
  988. }
  989.  
  990. // this function returns the voice index number which has the given voice name.
  991. // a zero is returned if there is no match
  992.  
  993. short    HsoiGetVoiceIndex( Str255 voiceStr )
  994. {
  995.     VoiceSpec            vSpec;
  996.     VoiceDescription    vDesc;
  997.     short                numVoices;
  998.     short                foundVoiceIndex = 0;
  999.     short                x = 1;
  1000.     OSErr                err;
  1001.     
  1002.     err = CountVoices( &numVoices );
  1003.     
  1004.     // find a matching voice index
  1005.     
  1006.     while ( (err == noErr) && (foundVoiceIndex == 0) && ( x <= numVoices ) )
  1007.     {
  1008.         err = GetIndVoice( x, &vSpec );
  1009.         
  1010.         if ( err == noErr )
  1011.             err = GetVoiceDescription( &vSpec, &vDesc, sizeof( VoiceDescription ) );
  1012.         
  1013.         // found a match?  we're done
  1014.         
  1015.         if ( ( err == noErr ) && ( EqualString( vDesc.name, voiceStr, false, false ) ) )
  1016.             foundVoiceIndex = x;
  1017.         else
  1018.             x++;
  1019.     }
  1020.     
  1021.     HsoiDisplaySpeechError( err );
  1022.     
  1023.     return foundVoiceIndex;
  1024. }
  1025.  
  1026. // this callback routine updates static variables which keep track of the position of the
  1027. // word being read so it can be hilited
  1028.  
  1029. pascal void HsoiMyWordCallback( SpeechChannel chan, long refCon, long wordPos, short wordLen )
  1030. {
  1031. #pragma unused( refCon, chan )
  1032.  
  1033.     sCBWordStart = sCBSelStart + wordPos;
  1034.     sCBWordEnd = sCBSelStart + wordPos + wordLen;
  1035.     
  1036.     return;
  1037. }
  1038.  
  1039. // this proceedure reads aloud the chosen text using the existing speech
  1040. // characterisstics.  a handle is created to hold the desired text.  it must stay
  1041. // locked until we are finished reading
  1042.  
  1043. void    HsoiReadTheText( long selStart, long selEnd, WEReference txtHdl )
  1044. {
  1045.     Ptr                txPtr;
  1046.     long            selLength;
  1047.     long            myA5;
  1048.     OSErr            err;
  1049.     SpeechWordUPP    speechWordUPP = nil;
  1050.     
  1051.     
  1052.     if ( txtHdl != nil )
  1053.     {
  1054.         // dispose of block of text being read, if any
  1055.         
  1056.         if ( sSpokenTxtHdl != nil )
  1057.             HsoiForgetHandle( &sSpokenTxtHdl );
  1058.         selLength = selEnd - selStart;
  1059.         err = HsoiNewHandleTemp( 0, &sSpokenTxtHdl );
  1060.         
  1061.         // create a new text handle and lock it
  1062.         
  1063.         if ( err == noErr )
  1064.             err = WECopyRange( selStart, selEnd, sSpokenTxtHdl, nil, nil, txtHdl );
  1065.         
  1066.         if ( err == noErr )
  1067.         {
  1068.             MoveHHi( sSpokenTxtHdl );
  1069.             HLock( sSpokenTxtHdl );
  1070.             txPtr = *sSpokenTxtHdl;
  1071.             
  1072.             // set up the callback routine to hilite words as they are spoken
  1073.             
  1074.             if ( gHiliting && (gHiliteMethod == word) )
  1075.             {
  1076.                 sCBSelStart = selStart;
  1077.                 sCBWordStart = selStart;
  1078.                 sCBWordEnd = selStart;
  1079.                 
  1080.                 // allows callback to access my globals
  1081.                 
  1082.                 // one interesting thing of note here.  this is taken from NIM:Sound (4-21)
  1083.                 
  1084.                 /*
  1085.                 
  1086.                     Unlike other selectors, the soCurrentA5 and soRefCon selectors do not
  1087.                     require that you pass a pointer to the information you are specifying
  1088.                     in the speechInfo parameter. Because an application’s A5 value and a
  1089.                     speech channel’s reference constant value are always each 4 bytes long
  1090.                     (the same size as the speechInfo parameter), your application passes
  1091.                     these values directly, casting them to pointer values
  1092.                 
  1093.                 */
  1094.                 
  1095.                 // hence why SetSpeechInfo ends with (Ptr)myA5 instead of something
  1096.                 // like &myA5.  same goes for the installation of the word proc.
  1097.                 // it's already (technically in 68k Macs) a typedef'd void * so we
  1098.                 // can just pass it "straight".
  1099.                 
  1100.                 myA5 = SetCurrentA5();
  1101.                 err = SetSpeechInfo( gSpeechChannel, soCurrentA5, (Ptr)myA5 );
  1102.                 
  1103.                 if ( err == noErr )
  1104.                 {
  1105.                     speechWordUPP = NewSpeechWordProc( HsoiMyWordCallback );
  1106.                     err = SetSpeechInfo( gSpeechChannel, soWordCallBack, speechWordUPP );
  1107.                 }
  1108.             }
  1109.             
  1110.             // now start talking
  1111.             
  1112.             err = SpeakText( gSpeechChannel, txPtr, selLength );
  1113.         }
  1114.         
  1115.         // if hiliting, fix the menu bar
  1116.         
  1117.         if ( gHiliting )
  1118.             HsoiAdjustMenus();
  1119.         
  1120.         if ( err != noErr )
  1121.         {
  1122.             HsoiDisplaySpeechError( err );
  1123.             gHiliting = false;
  1124.         }
  1125.         
  1126.         if ( speechWordUPP != nil )
  1127.             DisposeRoutineDescriptor( speechWordUPP );
  1128.     } // end: if ( txtHdl != nil )
  1129.     
  1130.     return;
  1131. }
  1132.  
  1133.  
  1134. // read the entire document
  1135.  
  1136. void    HsoiDoReadDoc( WindowRef window )
  1137. {
  1138.     WEReference        txtHdl;
  1139.     long            selStart, selEnd;
  1140.     
  1141.     if ( window != nil )
  1142.     {
  1143.         // remember which window is being read
  1144.         
  1145.         sTalkingWPtr = window;
  1146.         
  1147.         txtHdl = HsoiGetWindowWE( window );
  1148.         
  1149.         gPaused = false;
  1150.         
  1151.         switch( gHiliteMethod )
  1152.         {
  1153.             case off:
  1154.                 gHiliting = false;
  1155.                 selStart = 0;
  1156.                 selEnd = WEGetTextLength( txtHdl );
  1157.                 HsoiReadTheText( selStart, selEnd, txtHdl );
  1158.             break;
  1159.             
  1160.             case word:
  1161.                 gHiliting = true;
  1162.                 selStart = 0;
  1163.                 selEnd = WEGetTextLength( txtHdl );
  1164.                 HsoiReadTheText( selStart, selEnd, txtHdl );
  1165.             break;
  1166.             
  1167.             case sentence:
  1168.                 gHiliting = true;
  1169.                 WESetSelection( 0, 0, txtHdl );
  1170.             break;
  1171.         }
  1172.     
  1173.     }
  1174.     
  1175.     return;
  1176. }
  1177.  
  1178. // read the document starting at the cursor position
  1179.  
  1180. void    HsoiDoReadFromCursor( WindowRef window )
  1181. {
  1182.     WEReference        txtHdl;
  1183.     long            selStart, selEnd;
  1184.     
  1185.     if ( window != nil )
  1186.     {
  1187.         // remember which window is being read
  1188.         
  1189.         sTalkingWPtr = window;
  1190.         
  1191.         txtHdl = HsoiGetWindowWE( window );
  1192.         
  1193.         gPaused = false;
  1194.         
  1195.         WEGetSelection( &selStart, &selEnd, txtHdl );
  1196.         
  1197.         switch( gHiliteMethod )
  1198.         {
  1199.             case off:
  1200.                 gHiliting = false;
  1201.                 selEnd = WEGetTextLength( txtHdl );
  1202.                 HsoiReadTheText( selStart, selEnd, txtHdl );
  1203.             break;
  1204.             
  1205.             case word:
  1206.                 gHiliting = true;
  1207.                 selEnd = WEGetTextLength( txtHdl );
  1208.                 HsoiReadTheText( selStart, selEnd, txtHdl );
  1209.             break;
  1210.             
  1211.             case sentence:
  1212.                 gHiliting = true;
  1213.                 WESetSelection( selStart, selEnd, txtHdl );
  1214.             break;
  1215.         }
  1216.     }
  1217.  
  1218.     return;
  1219. }
  1220.  
  1221.  
  1222. // read the selected text
  1223.  
  1224. void    HsoiDoReadSelection( WindowRef window )
  1225. {
  1226.     WEReference        txtHdl;
  1227.     long            selStart, selEnd;
  1228.     
  1229.     if ( window != nil )
  1230.     {
  1231.         gPaused = false;
  1232.         gHiliting = false;
  1233.         
  1234.         txtHdl = HsoiGetWindowWE( window );
  1235.         
  1236.         WEGetSelection( &selStart, &selEnd, txtHdl );
  1237.         
  1238.         HsoiReadTheText( selStart, selEnd, txtHdl );
  1239.     }
  1240.     return;
  1241. }
  1242.  
  1243.  
  1244. // stop all sound playback and reading
  1245.  
  1246. OSErr    HsoiDoStop( void )
  1247. {
  1248.     OSErr        err = noErr;
  1249.     
  1250.     if ( gHasSpeechManager )
  1251.     {
  1252.         if ( ( SpeechBusy() > 0 ) || gPaused || gHiliting )
  1253.         {
  1254.             // stop talking now
  1255.             
  1256.             err = StopSpeech( gSpeechChannel );
  1257.             HsoiDisplaySpeechError( err );
  1258.             
  1259.             // release the memory
  1260.             
  1261.             if ( sSpokenTxtHdl != nil )
  1262.                 HsoiForgetHandle( &sSpokenTxtHdl );
  1263.             
  1264.             sTalkingWPtr = nil;
  1265.             gHiliting = false;
  1266.             gPaused = false;
  1267.         }
  1268.     }
  1269.     
  1270.     // stop all other sound
  1271.     
  1272.     if ( SoundIsPlaying() )
  1273.         StopCurrentSound();
  1274.     
  1275.     HsoiAdjustMenus();
  1276.     
  1277.     return err;
  1278. }
  1279.  
  1280.  
  1281. // pause/resume talking
  1282.  
  1283. void    HsoiDoPause( void )
  1284. {
  1285.     OSErr        err;
  1286.     
  1287.     if ( gPaused )
  1288.     {
  1289.         // resume talking
  1290.         
  1291.         err = ContinueSpeech( gSpeechChannel );
  1292.         gPaused = false;
  1293.     }
  1294.     else
  1295.     {
  1296.         // pause speech
  1297.         
  1298.         err = PauseSpeechAt( gSpeechChannel, kImmediate );
  1299.         gPaused = true;
  1300.     }
  1301.     
  1302.     HsoiDisplaySpeechError( err );
  1303.  
  1304.     return;
  1305. }
  1306.  
  1307. // handle the "Turn Speech On/Off" menu item
  1308.  
  1309. void    HsoiDoSpeechOnOff( void )
  1310. {
  1311.     VoiceSpec        vSpec;
  1312.     short            voiceIndex;
  1313.     OSErr            err = noErr;
  1314.     
  1315.     // turn speech off, release memory
  1316.     
  1317.     if ( gSpeechOn )
  1318.     {
  1319.         err = HsoiDoStop();
  1320.         
  1321.         if ( (err == noErr) && (gSpeechChannel != nil) )
  1322.         {
  1323.             err = DisposeSpeechChannel( gSpeechChannel );
  1324.             gSpeechChannel = nil;
  1325.         }
  1326.         
  1327.         gSpeechOn = false;
  1328.     }
  1329.     else
  1330.     {
  1331.         // turn speech on, make sure speech manager is installed
  1332.         
  1333.         // we should have more informative error handling than this, but the adjust
  1334.         // menus code ought to be well enough written that if there is no Speech Manager,
  1335.         // the "Turn Speech On" menu item should never be an option (never be hilited/enabled)
  1336.         
  1337.         if ( !gHasSpeechManager )
  1338.             HsoiDisplaySpeechError( -1 );
  1339.         else
  1340.         {
  1341.             voiceIndex = HsoiGetVoiceIndex( gMyPrefs.sVoiceStr );
  1342.             
  1343.             // use default voice and default settings
  1344.             
  1345.             if ( voiceIndex == 0 )
  1346.             {
  1347.                 err = NewSpeechChannel( nil, &gSpeechChannel );
  1348.                 if ( err == noErr )
  1349.                     err = GetSpeechRate( gSpeechChannel, &gMyPrefs.sVoiceRate );
  1350.                 if ( err == noErr )
  1351.                     err = GetSpeechPitch( gSpeechChannel, &gMyPrefs.sVoicePitch );
  1352.                 if ( err == noErr )
  1353.                     err = GetSpeechInfo( gSpeechChannel, soPitchMod, &gMyPrefs.sVoiceMod );
  1354.             }
  1355.             else
  1356.             {
  1357.                 // restsore preferred voice and settings
  1358.                 
  1359.                 err = GetIndVoice( voiceIndex, &vSpec );
  1360.                 
  1361.                 if ( err == noErr )
  1362.                     err = NewSpeechChannel( &vSpec, &gSpeechChannel );
  1363.                 if ( err == noErr )
  1364.                     err = SetSpeechRate( gSpeechChannel, gMyPrefs.sVoiceRate );
  1365.                 if ( err == noErr )
  1366.                     err = SetSpeechPitch( gSpeechChannel, gMyPrefs.sVoicePitch );
  1367.                 if ( err == noErr )
  1368.                     err = SetSpeechInfo( gSpeechChannel, soPitchMod, &gMyPrefs.sVoiceMod );
  1369.             }
  1370.         
  1371.             // success!
  1372.             
  1373.             if ( err == noErr )
  1374.             {
  1375.                 gSpeechOn = true;
  1376.                 gHiliting = false;
  1377.             }
  1378.         }
  1379.     }
  1380.     
  1381.     HsoiDisplaySpeechError( err );
  1382.  
  1383.     return;
  1384. }
  1385.  
  1386.  
  1387. // this procedure is called by HsoiDoMenuCommand to handle a selection in the AutoHighlight
  1388. // subment.  The word or sentence is hilited as it is spoken
  1389.  
  1390. void    HsoiDoHiliteMenu( short item )
  1391. {
  1392.     switch ( item )
  1393.     {
  1394.         case iHilitingNone:
  1395.             gHiliteMethod = off;
  1396.         break;
  1397.         
  1398.         case iHilitingWord:
  1399.             gHiliteMethod = word;
  1400.         break;
  1401.         
  1402.         case iHilitingSentence:
  1403.             gHiliteMethod = sentence;
  1404.         break;
  1405.         
  1406.         default:
  1407.             gHiliteMethod = off;
  1408.     }
  1409.     
  1410.     return;
  1411. }
  1412.  
  1413. // this procedure returns the selStart/selEnd of the next legal sentence, for hiliting purposes
  1414.  
  1415. void    HsoiGetNextSelection( long *selStart, long *selEnd, WEReference txtHdl )
  1416. {
  1417.     long            textLength;
  1418.     
  1419.     textLength = WEGetTextLength( txtHdl );
  1420.     
  1421.     // skip over leading blanks to find the start of the sentence
  1422.     
  1423.     while( (*selStart < textLength) && HsoiIsSpacer( *selStart, txtHdl, inSpacers ) )
  1424.         *selStart += 1;
  1425.     
  1426.     // now find the end of this sentence
  1427.     
  1428.     if ( *selStart < textLength )
  1429.         *selEnd = *selStart + 1;
  1430.     else
  1431.         *selEnd = *selStart;
  1432.         
  1433.     while( (*selEnd < textLength ) && !HsoiIsSpacer( *selEnd, txtHdl, inEnders ) )
  1434.         *selEnd += 1;
  1435.     
  1436.     if ( *selEnd < textLength )
  1437.         *selEnd += 1;
  1438.     
  1439.     return;
  1440. }
  1441.  
  1442. // given a handle to the text and an offset, find out if the given char (at that offset in
  1443. // the text) is a spacer (kSpacer) or not
  1444.  
  1445. Boolean    HsoiIsSpacer( long selStart, WEReference we, short inWhat )
  1446. {
  1447.     unsigned char    c;
  1448.     short            x;
  1449.     
  1450.     c = WEGetChar( selStart, we );
  1451.     
  1452.     if ( inWhat == inSpacers )
  1453.     {
  1454.         for ( x = 0; x < sizeof( kSpacers ); x++ )
  1455.         {
  1456.             if ( c == kSpacers[x] )
  1457.                 return true;
  1458.         }
  1459.         
  1460.         return false;
  1461.     }
  1462.     
  1463.     else if ( inWhat == inEnders )
  1464.     {
  1465.         for ( x = 0; x < sizeof( kSentenceEnders ); x++ )
  1466.         {
  1467.             if ( c == kSentenceEnders[x] )
  1468.                 return true;
  1469.         }
  1470.         
  1471.         return false;
  1472.     }
  1473.     
  1474.     return false;
  1475. }
  1476.  
  1477.  
  1478. // main RunHiliter routine.  called fromthe main event loop (gHiliting == true) when reading
  1479. // and hiliting the entire document, word by word, or sentence by sentence
  1480.  
  1481. void    HsoiRunHiliter( EventRecord *event )
  1482. {
  1483.     WindowRef        window;
  1484.     WEReference        we;
  1485.     long            selStart;
  1486.     long            selEnd;
  1487.     OSErr            err;
  1488.     
  1489.     window = FrontWindow();
  1490.     
  1491.     if ( EventAvail( everyEvent, event) || !HsoiIsDocumentWindow(window) || gPaused ||
  1492.             (window != sTalkingWPtr ) )
  1493.         return;
  1494.     
  1495.     we = HsoiGetWindowWE( window );
  1496.     WEGetSelection( &selStart, &selEnd, we );
  1497.     
  1498.     // autohiliting by word
  1499.     
  1500.     if ( gHiliteMethod == word )
  1501.     {
  1502.         // done with doc
  1503.         
  1504.         if ( SpeechBusy() == 0 )
  1505.             err = HsoiDoStop();
  1506.         else if ( sCBWordEnd != selEnd )
  1507.             WESetSelection( sCBWordStart, sCBWordEnd, we ); // don't reselect the same word
  1508.     }
  1509.     else if ( SpeechBusy() > 0 ) // autohiliting by sentence...not done with current sentence yet
  1510.         return;
  1511.     
  1512.     else if ( selEnd == WEGetTextLength( we ) ) // finished reading doc
  1513.         err = HsoiDoStop();
  1514.     
  1515.     else
  1516.     {
  1517.         // hilite and read the next sentence
  1518.         
  1519.         selStart = selEnd;
  1520.         HsoiGetNextSelection( &selStart, &selEnd, we );
  1521.         WESetSelection( selStart, selEnd, we );
  1522.         HsoiReadTheText( selStart, selEnd, we );
  1523.     }
  1524.     
  1525.     return;
  1526. }
  1527.  
  1528. // this routine is called by the various HsoiAdjustMenu() functions to make sure the
  1529. // sound menu looks proper.
  1530.  
  1531. void    HsoiAdjustSoundMenu( WindowRef window )
  1532. {
  1533.     MenuRef                soundMenu, hilitingMenu;
  1534.     short                i;
  1535.     OSErr                isOnlySoundObject;
  1536.     WEObjectReference    objectRef;
  1537.     Str255                onOffStr, pauseResumeStr;
  1538.     WEReference            we;
  1539.     Boolean                disableSpeakCursor;
  1540.     
  1541.     
  1542.     soundMenu = GetMenuHandle( mSound );
  1543.     hilitingMenu = GetMenuHandle( mHiliting );
  1544.     
  1545.     // if we don't have a window that can have it's text spoken (i.e not a document
  1546.     // window), it's simple enough...just disable it all.  the one thing that i would
  1547.     // think to have enabled would be to turn speech on or off, but if we did that,
  1548.     // the nonverbal cues/feedback given to the user via the GUI would possibly have them
  1549.     // think they could speak what's in this "odd" window.  so, it's best that if there's
  1550.     // no window or it's a non-speakable window, disable the whole schebang.
  1551.     
  1552.     if ( (window == nil) || ((!HsoiIsDocumentWindow(window)) && (!HsoiIsClipboardWindow(window))) )
  1553.     {        
  1554.         DisableItem( soundMenu, 0 );
  1555.         return;
  1556.     }
  1557.  
  1558.     // we know we have a valid window, so enable/disable items as needed
  1559.     
  1560.     we = HsoiGetWindowWE( window );
  1561.     
  1562.     // check if the WASTE instance is read-only.  if so, and if WASTE_NO_RO_CARET is defined
  1563.     // as true, then in a read-only instance there is no cursor to read from (so we'll
  1564.     // always disable "Speak from Cursor")
  1565.  
  1566. #if WASTE_NO_RO_CARET
  1567.  
  1568.     // since we have the precompiler directive on, if it's a read-only instance then
  1569.     // the caret will be hid
  1570.     
  1571.     disableSpeakCursor = WEFeatureFlag( weFReadOnly, weBitTest, we );
  1572.  
  1573. #else
  1574.     
  1575.     // now whether it's read only or not, there will be a caret so we can disable/enable
  1576.     // the "Speak from Cursor" "normally"
  1577.     
  1578.     disableSpeakCursor = false;
  1579.  
  1580. #endif
  1581.     
  1582.     // first, enable everything, disable as we go
  1583.     
  1584.     for ( i = 0; i <= CountMenuItems( soundMenu ); i++ )
  1585.         EnableItem( soundMenu, i );
  1586.     for ( i = 0; i<= CountMenuItems( hilitingMenu ); i++ )
  1587.         EnableItem( hilitingMenu, i );
  1588.         
  1589.     // get the toggle strings all set up and the menu displaying the correct text
  1590.     
  1591.     if ( gPaused )
  1592.         GetIndString( pauseResumeStr, kVoiceStringsID, kStrResume );
  1593.     else
  1594.         GetIndString( pauseResumeStr, kVoiceStringsID, kStrPause );
  1595.     SetMenuItemText( soundMenu, iPauseSpeaking, pauseResumeStr );
  1596.     
  1597.     if ( gSpeechOn )
  1598.         GetIndString( onOffStr, kVoiceStringsID, kStrTurnOff );
  1599.     else
  1600.     {
  1601.         if ( gHasSpeechManager )
  1602.             GetIndString( onOffStr, kVoiceStringsID, kStrTurnOn );
  1603.         else
  1604.             GetIndString( onOffStr, kVoiceStringsID, kStrNoSpeechMgr );
  1605.     }
  1606.     
  1607.     SetMenuItemText( soundMenu, iTurnSpeechOnOff, onOffStr );
  1608.     
  1609.     // and go ahead and adjust the "Speak from Cursor" item
  1610.     
  1611.     if ( disableSpeakCursor )
  1612.         DisableItem( GetMenuHandle( mSound ), iSpeakFromCursor );
  1613.     
  1614.     // now, we do not want the ability to play a sound while text is being spoken, nor
  1615.     // do we want to speak text while a sound is being played.  so, there are basically
  1616.     // 3 "modes" to check for:
  1617.     
  1618.     // no sound playing nor speech talking
  1619.     // a sound playing
  1620.     // speech talking
  1621.     
  1622.     // first, we'll check for a sound playing
  1623.     
  1624.     if ( SoundIsPlaying() )
  1625.     {
  1626.         // ok, a sound is playing, most everything gets disabled
  1627.         
  1628.         // obviously we can't record
  1629.         
  1630.         DisableItem( soundMenu, iRecordNewSnd );
  1631.  
  1632.         // enable Play Sound if and only if a sound object is selected, and if the object
  1633.         // selected it a 'snd ' object (you'd think we should check to see if a sound
  1634.         // is currently playing, but that's not necessary since Kamprath's Object Handlers
  1635.         // can play sound async and starting a sound with one playing will stop any currntly
  1636.         // playing sound (e.g. play one sound, while playing select another, start playing
  1637.         // it cause this first sound is getting boring).
  1638.         
  1639.         isOnlySoundObject = WEGetSelectedObject( &objectRef, we );
  1640.         
  1641.         if ((!isOnlySoundObject) && ( WEGetObjectType( objectRef ) == TYPE_SOUND) )
  1642.             EnableItem( soundMenu, iPlaySnd );
  1643.         else
  1644.             DisableItem( soundMenu, iPlaySnd );
  1645.             
  1646.  
  1647.         // of course, we can stop a playing sound
  1648.         
  1649.         EnableItem( soundMenu, iStopPlayingSnd );
  1650.         
  1651.         // and then everything dealing with text to speech should be disabled            
  1652.     
  1653.         DisableItem( soundMenu, iSpeakAll );
  1654.         DisableItem( soundMenu, iSpeakFromCursor );
  1655.         DisableItem( soundMenu, iSpeakSelection );
  1656.         DisableItem( soundMenu, iStopSpeaking );
  1657.         DisableItem( soundMenu, iPauseSpeaking );
  1658.         DisableItem( soundMenu, iTurnSpeechOnOff );
  1659.         DisableItem( soundMenu, iAutoHiliting );
  1660.         DisableItem( soundMenu, iSpeechOptions );
  1661.         
  1662.         // and then just return, we're done
  1663.         
  1664.         return;
  1665.     }
  1666.         
  1667.     if ( !gSpeechOn )
  1668.     {
  1669.         // we don't even have speech turned on, so this is pretty simple too.  we
  1670.         // also know by now that there isn't a sound playing (if there was, we should
  1671.         // have hit the previous if statement)
  1672.         
  1673.         // also, this "if" block is the place to catch things if the Speech Manager
  1674.         // isn't even installed on the user's machine.  if no speech manager, gSpeechOn
  1675.         // will always be false and we'll always come into this if statement (unless
  1676.         // there is a sound playing, and in that case, we hit the previous if
  1677.         // and the text-to-speech stuff is disabled anyways)
  1678.         
  1679.         // first, deal with the sound items
  1680.         
  1681.         // can we record?
  1682.         
  1683.         if ( !gCanRecordSound )
  1684.             DisableItem( soundMenu, iRecordNewSnd );
  1685.         else
  1686.         {
  1687.             // there are capabilities to record sound, but do we want to?  depends
  1688.             // on the front window
  1689.             
  1690.             if ( HsoiIsClipboardWindow( window ) )
  1691.                 DisableItem( soundMenu, iRecordNewSnd );
  1692.         }
  1693.         // can we play a sound?
  1694.  
  1695.         isOnlySoundObject = WEGetSelectedObject( &objectRef, we );
  1696.         
  1697.         if ((!isOnlySoundObject) && ( WEGetObjectType( objectRef ) == TYPE_SOUND) )
  1698.             EnableItem( soundMenu, iPlaySnd );
  1699.         else
  1700.             DisableItem( soundMenu, iPlaySnd );
  1701.         
  1702.         // can we stop a sound?  we shouldn't be able to cause there should be no
  1703.         // sounds playing
  1704.         
  1705.         DisableItem( soundMenu, iStopPlayingSnd );
  1706.         
  1707.         // and since speech isn't turned on, everything but the "Turn On" item
  1708.         // can be disabled
  1709.         
  1710.         DisableItem( soundMenu, iSpeakAll );
  1711.         DisableItem( soundMenu, iSpeakFromCursor );
  1712.         DisableItem( soundMenu, iSpeakSelection );
  1713.         DisableItem( soundMenu, iStopSpeaking );
  1714.         DisableItem( soundMenu, iPauseSpeaking );
  1715.         DisableItem( soundMenu, iAutoHiliting );
  1716.         DisableItem( soundMenu, iSpeechOptions );
  1717.         
  1718.         if ( !gHasSpeechManager )
  1719.             DisableItem( soundMenu, iTurnSpeechOnOff );
  1720.         
  1721.         // and we can just return
  1722.         
  1723.         return;
  1724.     }    // end: if (!gSpeechOn)
  1725.     
  1726.     // ok, so by now we know a few things:  there are no currently playing sounds,
  1727.     // and speech must be on.
  1728.     
  1729.     // ingoring playing sounds for a moment, there are a few cases within gSpeechOn == true
  1730.     // that we have to contend with:
  1731.     
  1732.     // if we are currently speaking
  1733.     // if we're not speaking yet
  1734.     // if speech is paused
  1735.     // if we're hiliting
  1736.     
  1737.     // so, let's get to it
  1738.     
  1739.     if ( gSpeechOn )
  1740.     {
  1741.         // we know speech is on, and there shouldn't be any sounds playing
  1742.         
  1743.         // now, if we're already speaking, adjust accordingly
  1744.         
  1745.         if ( (SpeechBusy() > 0) || gHiliting || gPaused )
  1746.         {
  1747.             DisableItem( soundMenu, iSpeakAll );
  1748.             DisableItem( soundMenu, iSpeakFromCursor );
  1749.             DisableItem( soundMenu, iSpeakSelection );
  1750.             // stop and pause should already be enabled (and the pause item should have the
  1751.             // correct menu item text)
  1752.             DisableItem( soundMenu, iAutoHiliting );
  1753.             DisableItem( soundMenu, iSpeechOptions );
  1754.             
  1755.             // and since we know we're speaking, we can disable all the sound stuff
  1756.             
  1757.             DisableItem( soundMenu, iRecordNewSnd );
  1758.             DisableItem( soundMenu, iPlaySnd );
  1759.             DisableItem( soundMenu, iStopPlayingSnd );
  1760.         }
  1761.         else
  1762.         {
  1763.             long selStart, selEnd;
  1764.             
  1765.             // we're not speaking yet
  1766.             
  1767.             // Speak all and speak from cursor should already be enabled
  1768.             
  1769.             // check if we have a selection, and if not, disable Speak Selection
  1770.             
  1771.             WEGetSelection( &selStart, &selEnd, we );
  1772.             
  1773.             if ( selStart == selEnd )
  1774.                 DisableItem( soundMenu, iSpeakSelection );
  1775.             
  1776.             DisableItem( soundMenu, iStopSpeaking );
  1777.             DisableItem( soundMenu, iPauseSpeaking );
  1778.             
  1779.             // and turn on/off, autohiliting, and speech options should already be
  1780.             // enabled
  1781.             
  1782.             // do the auto-hiliting sub menu
  1783.             
  1784.             // enable it all
  1785.             
  1786.             for ( i = 0; i <= CountMenuItems( hilitingMenu ); i++ )
  1787.                 EnableItem( hilitingMenu, i );
  1788.             
  1789.             // set the check marks
  1790.             
  1791.             CheckItem( hilitingMenu, iHilitingNone, (gHiliteMethod == off) );
  1792.             CheckItem( hilitingMenu, iHilitingWord, (gHiliteMethod == word) );
  1793.             CheckItem( hilitingMenu, iHilitingSentence, (gHiliteMethod == sentence) );
  1794.             
  1795.             // and do the sound items
  1796.             
  1797.             // we know there are no currently playing sounds....
  1798.             
  1799.             if ( !gCanRecordSound )
  1800.                 DisableItem( soundMenu, iRecordNewSnd );
  1801.             else
  1802.             {
  1803.                 if ( HsoiIsClipboardWindow( window ) )
  1804.                     DisableItem( soundMenu, iRecordNewSnd );
  1805.             }
  1806.             
  1807.             // enable Play Sound if we can
  1808.             
  1809.             // can we play a sound?
  1810.  
  1811.             isOnlySoundObject = WEGetSelectedObject( &objectRef, we );
  1812.         
  1813.             if ((!isOnlySoundObject) && ( WEGetObjectType( objectRef ) == TYPE_SOUND) )
  1814.                 EnableItem( soundMenu, iPlaySnd );
  1815.             else
  1816.                 DisableItem( soundMenu, iPlaySnd );
  1817.         
  1818.             // can we stop a sound?  we shouldn't be able to cause there should be no
  1819.             // sounds playing
  1820.         
  1821.             DisableItem( soundMenu, iStopPlayingSnd );
  1822.         }
  1823.         
  1824.         // we have to check if spoken text is being hilited.  if so, we cannot allow
  1825.         // much else to occur.  basically, disable everything.
  1826.         
  1827.         if ( gHiliting )
  1828.         {
  1829.             DisableItem( GetMenuHandle( mApple ), 0 );
  1830.             DisableItem( GetMenuHandle( mFile ), 0 );
  1831.             DisableItem( GetMenuHandle( mEdit ), 0 );
  1832.             DisableItem( GetMenuHandle( mText ), 0 );
  1833.             DisableItem( GetMenuHandle( mDialogs ), 0 );
  1834.             DisableItem( GetMenuHandle( mWindows ), 0 );
  1835.         }            
  1836.         
  1837.     }// end: if ( gSpeechOn )
  1838.  
  1839.  
  1840.     // and hopefully, that does it all just right!
  1841.  
  1842.  
  1843.     return;
  1844. }